package com.gotokeep.keep.composition.renderer;

import android.media.MediaCodec;
import android.media.MediaFormat;
import android.os.Build;
import android.support.annotation.CallSuper;
import android.util.Log;

import com.gotokeep.keep.composition.MediaExportConfiguration;
import com.gotokeep.keep.composition.RenderSink;

import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * @author xana/cuixianming
 * @version 1.0
 * @since 2018-05-03 18:10
 */
public abstract class MediaRenderer {
    private static final long TIMEOUT_US = 1000L;
    protected static final String TAG = MediaRenderer.class.getSimpleName();
    private boolean end = false;

    MediaCodec encoder;
    boolean endOfStream;
    private MediaFormat encodeFormat;
    private MediaCodec.BufferInfo encoderBufferInfo;
    private int muxerTrackIndex;
    private int outputBufferIndex;
    RenderSink sink;

    boolean verbose;

    public abstract long render(long presentationTimeUs);

    public void setRenderSink(RenderSink sink) {
        this.sink = sink;
    }

    @CallSuper
    public void prepareEncoder(MediaExportConfiguration configuration) throws IOException {
        encoderBufferInfo = new MediaCodec.BufferInfo();
    }

    public void releaseEncoder() {
        if (encoder != null) {
            encoder.stop();
            encoder.release();
            encoder = null;
        }
    }

    public void encodeFrame() throws RuntimeException {
        boolean encoded = false;
        releaseOutputBuffer();
        while (!encoded) {
            outputBufferIndex = encoder.dequeueOutputBuffer(encoderBufferInfo, TIMEOUT_US);
            switch (outputBufferIndex) {
                case MediaCodec.INFO_TRY_AGAIN_LATER:
                    if (endOfStream) {
                        end = true;
                        return;
                    } else {

                    }
                    break;
                case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                    break;
                case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                    if (encodeFormat != null) {
                        throw new RuntimeException("Format change twice.");
                    }
                    encodeFormat = encoder.getOutputFormat();
                    if (verbose) {
                        Log.v(TAG, "Encoder output format changed: " + encodeFormat);
                    }
                    encoded = true;
                    break;
                default:
                    if (!isReady()) {
                        throw new RuntimeException("MediaMuxer has not started");
                    }
                    encoded = true;
            }
        }
    }

    public void releaseOutputBuffer() {
        if (isReady() && outputBufferIndex >= 0) {
            encoder.releaseOutputBuffer(outputBufferIndex, false);
            outputBufferIndex = -1;
        }
    }

    public ByteBuffer getOutputBuffer() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            return encoder.getOutputBuffer(outputBufferIndex);
        } else {
            return encoder.getOutputBuffers()[outputBufferIndex];
        }
    }

    public MediaCodec.BufferInfo getOutputBufferInfo() {
        return encoderBufferInfo;
    }

    public int getMuxerTrackIndex() {
        return muxerTrackIndex;
    }

    public void setMuxerTrackIndex(int trackIndex) {
        this.muxerTrackIndex = trackIndex;
    }

    public MediaFormat getOutputFormat() {
        return encodeFormat;
    }


    public boolean isReady() {
        return getMuxerTrackIndex() >= 0 && getOutputFormat() != null;
    }

    protected void setEnd(boolean end) {
        this.end = end;
    }

    public boolean isEnd() {
        return end;
    }

    public void setVerbose(boolean verbose) {
        this.verbose = verbose;
    }
}
